/*
 * This file is part of "JTA - Telnet/SSH for the JAVA(tm) platform".
 *
 * (c) Matthias L. Jugel, Marcus Meißner 1996-2005. All Rights Reserved.
 *
 * Please visit http://javatelnet.org/ for updates and contact.
 *
 * --LICENSE NOTICE--
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 * --LICENSE NOTICE--
 *
 */

package de.mud.ssh;

import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

/**
 * Secure Shell IO
 * 
 * @author Marcus Meissner
 * @version $Id: SshIO.java 506 2005-10-25 10:07:21Z marcus $
 */
public abstract class SshIO
{

    private static MessageDigest md5;

    static
    {
	try
	{
	    md5 = MessageDigest.getInstance("MD5");
	}
	catch (NoSuchAlgorithmException e)
	{
	    System.err
		    .println("SshIO: unable to load message digest algorithm: "
			    + e);
	    e.printStackTrace();
	}
    }

    /**
     * variables for the connection
     */
    private String idstr = ""; // (
			       // "SSH-<protocolmajor>.<protocolminor>-<version>\n"
			       // )
    private String idstr_sent = "SSH/JTA (c) Marcus Meissner, Matthias L. Jugel\n";

    /**
     * Debug level. This results in additional diagnostic messages on the java
     * console.
     */
    private static int debug = 0;

    /**
     * State variable for Ssh negotiation reader
     */
    private SshCrypto crypto = null;

    String cipher_type = "IDEA";

    private int remotemajor, remoteminor;
    private int mymajor, myminor;
    private int useprotocol;

    private String login = "", password = "";
    // nobody is to access those fields : better to use pivate, nobody knows :-)

    public String dataToSend = null;

    public String hashHostKey = null; // equals to the applet parameter if any

    byte lastPacketSentType;

    // phase : handleBytes
    private int phase = 0;
    private final int PHASE_INIT = 0;
    private final int PHASE_SSH_RECEIVE_PACKET = 1;

    // SSH v2 RSA
    BigInteger rsa_e, rsa_n;

    // handlePacket
    // messages
    // The supported packet types and the corresponding message numbers are
    // given in the following table. Messages with _MSG_ in their name may
    // be sent by either side. Messages with _CMSG_ are only sent by the
    // client, and messages with _SMSG_ only by the server.
    //
    private final byte SSH_MSG_DISCONNECT = 1;
    private final byte SSH_SMSG_PUBLIC_KEY = 2;
    private final byte SSH_CMSG_SESSION_KEY = 3;
    private final byte SSH_CMSG_USER = 4;
    private final byte SSH_CMSG_AUTH_PASSWORD = 9;
    private final byte SSH_CMSG_REQUEST_PTY = 10;
    private final byte SSH_CMSG_WINDOW_SIZE = 11;
    private final byte SSH_CMSG_EXEC_SHELL = 12;
    private final byte SSH_SMSG_SUCCESS = 14;
    private final byte SSH_SMSG_FAILURE = 15;
    private final byte SSH_CMSG_STDIN_DATA = 16;
    private final byte SSH_SMSG_STDOUT_DATA = 17;
    private final byte SSH_SMSG_STDERR_DATA = 18;
    private final byte SSH_SMSG_EXITSTATUS = 20;
    private final byte SSH_MSG_IGNORE = 32;
    private final byte SSH_CMSG_EXIT_CONFIRMATION = 33;
    private final byte SSH_MSG_DEBUG = 36;

    /* SSH v2 stuff */

    private final byte SSH2_MSG_DISCONNECT = 1;
    private final byte SSH2_MSG_IGNORE = 2;
    private final byte SSH2_MSG_SERVICE_REQUEST = 5;
    private final byte SSH2_MSG_SERVICE_ACCEPT = 6;

    private final byte SSH2_MSG_KEXINIT = 20;
    private final byte SSH2_MSG_NEWKEYS = 21;

    private final byte SSH2_MSG_KEXDH_INIT = 30;
    private final byte SSH2_MSG_KEXDH_REPLY = 31;

    private String kexalgs, hostkeyalgs, encalgs2c, encalgc2s, macalgs2c,
	    macalgc2s, compalgc2s, compalgs2c, langc2s, langs2;

    private int outgoingseq = 0, incomingseq = 0;

    //
    // encryption types
    //
    private int SSH_CIPHER_NONE = 0; // No encryption
    private int SSH_CIPHER_IDEA = 1; // IDEA in CFB mode (patented)
    private int SSH_CIPHER_DES = 2; // DES in CBC mode
    private int SSH_CIPHER_3DES = 3; // Triple-DES in CBC mode
    private int SSH_CIPHER_TSS = 4; // An experimental stream cipher

    private int SSH_CIPHER_RC4 = 5; // RC4 (patented)

    private int SSH_CIPHER_BLOWFISH = 6; // Bruce Scheiers blowfish (public d)

    //
    // authentication methods
    //
    private final int SSH_AUTH_RHOSTS = 1; // .rhosts or /etc/hosts.equiv
    private final int SSH_AUTH_RSA = 2; // pure RSA authentication
    private final int SSH_AUTH_PASSWORD = 3; // password authentication,
					     // implemented !
    private final int SSH_AUTH_RHOSTS_RSA = 4; // .rhosts with RSA host
					       // authentication

    private boolean cansenddata = false;

    /**
     * Initialise SshIO
     */
    public SshIO()
    {
	crypto = null;
    }

    public void setLogin(String user)
    {
	if (user == null)
	    user = "";
	login = user;
    }

    public void setPassword(String password)
    {
	if (password == null)
	    password = "";
	this.password = password;
    }

    SshPacket currentpacket;

    protected abstract void write(byte[] buf) throws IOException;

    public abstract String getTerminalType();

    byte[] one = new byte[1];

    private void write(byte b) throws IOException
    {
	one[0] = b;
	write(one);
    }

    public void disconnect()
    {
	// System.err.println("In Disconnect");
	idstr = "";
	login = "";
	password = "";
	phase = 0;
	crypto = null;
    }

    public void setWindowSize(int columns, int rows) throws IOException
    {
	if (phase == PHASE_INIT)
	{
	    System.err
		    .println("sshio:setWindowSize(), sizing in init phase not supported.\n");
	}
	if (debug > 1)
	    System.err.println("SSHIO:setWindowSize(" + columns + "," + rows
		    + ")");
	Send_SSH_CMSG_WINDOW_SIZE(columns, rows);
    }

    synchronized public void sendData(String str) throws IOException
    {
	if (debug > 1)
	    System.out.println("SshIO.send(" + str + ")");
	if (dataToSend == null)
	    dataToSend = str;
	else
	    dataToSend += str;
	if (cansenddata)
	{
	    Send_SSH_CMSG_STDIN_DATA(dataToSend);
	    dataToSend = null;
	}
    }

    /**
     * Read data from the remote host. Blocks until data is available.
     * 
     * Returns an array of bytes that will be displayed.
     * 
     */
    public byte[] handleSSH(byte buff[]) throws IOException
    {
	byte[] rest;
	String result;

	if (debug > 1)
	    System.out.println("SshIO.getPacket(" + buff + "," + buff.length
		    + ")");

	if (phase == PHASE_INIT)
	{
	    byte b; // of course, byte is a signed entity (-128 -> 127)
	    int boffset = 0; // offset into the buffer received

	    while (boffset < buff.length)
	    {
		b = buff[boffset++];
		// both sides MUST send an identification string of the form
		// "SSH-protoversion-softwareversion comments",
		// followed by newline character(ascii 10 = '\n' or '\r')
		idstr += (char) b;
		if (b == '\n')
		{
		    if (!idstr.substring(0, 4).equals("SSH-"))
		    {
			// we need to ignore lines of data that precede the
			// idstr
			if (debug > 0)
			    System.out.print("Received data line: " + idstr);
			idstr = "";
			continue;
		    }
		    phase++;
		    remotemajor = Integer.parseInt(idstr.substring(4, 5));
		    String minorverstr = idstr.substring(6, 8);
		    if (!Character.isDigit(minorverstr.charAt(1)))
			minorverstr = minorverstr.substring(0, 1);
		    remoteminor = Integer.parseInt(minorverstr);

		    System.out.println("remotemajor " + remotemajor);
		    System.out.println("remoteminor " + remoteminor);

		    if (remotemajor == 2)
		    {
			mymajor = 2;
			myminor = 0;
			useprotocol = 2;
		    }
		    else
		    {
			if (false && (remoteminor == 99))
			{
			    mymajor = 2;
			    myminor = 0;
			    useprotocol = 2;
			}
			else
			{
			    mymajor = 1;
			    myminor = 5;
			    useprotocol = 1;
			}
		    }
		    // this is how we tell the remote server what protocol we
		    // use.
		    idstr_sent = "SSH-" + mymajor + "." + myminor + "-"
			    + idstr_sent;
		    write(idstr_sent.getBytes());

		    if (useprotocol == 2)
			currentpacket = new SshPacket2(null);
		    else
			currentpacket = new SshPacket1(null);
		}
	    }
	    if (boffset == buff.length)
		return "".getBytes();
	    return "Must not have left over data after PHASE_INIT!\n"
		    .getBytes();
	}

	result = "";
	rest = currentpacket.addPayload(buff);
	if (currentpacket.isFinished())
	{
	    if (useprotocol == 1)
	    {
		result = result + handlePacket1((SshPacket1) currentpacket);
		currentpacket = new SshPacket1(crypto);
	    }
	    else
	    {
		result = result + handlePacket2((SshPacket2) currentpacket);
		currentpacket = new SshPacket2(crypto);
	    }
	}
	while (rest != null)
	{
	    rest = currentpacket.addPayload(rest);
	    if (currentpacket.isFinished())
	    {
		// the packet is finished, otherwise we would not have got a
		// rest
		if (useprotocol == 1)
		{
		    result = result + handlePacket1((SshPacket1) currentpacket);
		    currentpacket = new SshPacket1(crypto);
		}
		else
		{
		    result = result + handlePacket2((SshPacket2) currentpacket);
		    currentpacket = new SshPacket2(crypto);
		}
	    }
	}
	return result.getBytes();
    }

    /**
     * Handle SSH protocol Version 2
     * 
     * @param p
     *            the packet we will process here.
     * @return a array of bytes
     */
    private String handlePacket2(SshPacket2 p) throws IOException
    {
	switch (p.getType())
	{
	case SSH2_MSG_IGNORE:
	    System.out.println("SSH2: SSH2_MSG_IGNORE");
	    break;
	case SSH2_MSG_DISCONNECT:
	    int discreason = p.getInt32();
	    String discreason1 = p.getString();
	    /* String discreason2 = p.getString(); */
	    System.out.println("SSH2: SSH2_MSG_DISCONNECT(" + discreason + ","
		    + discreason1 + "," + /* discreason2+ */")");

	    return "\nSSH2 disconnect: " + discreason1 + "\n";

	case SSH2_MSG_NEWKEYS:
	{
	    System.out.println("SSH2: SSH2_MSG_NEWKEYS");
	    sendPacket2(new SshPacket2(SSH2_MSG_NEWKEYS));

	    byte[] session_key = new byte[16];

	    crypto = new SshCrypto(cipher_type, session_key);

	    SshPacket2 pn = new SshPacket2(SSH2_MSG_SERVICE_REQUEST);
	    pn.putString("ssh-userauth");
	    sendPacket2(pn);
	    break;
	}
	case SSH2_MSG_SERVICE_ACCEPT:
	{
	    System.out.println("Service Accept: " + p.getString());
	    break;
	}
	case SSH2_MSG_KEXINIT:
	{
	    byte[] fupp;
	    System.out.println("SSH2: SSH2_MSG_KEXINIT");
	    byte kexcookie[] = p.getBytes(16); // unused.

	    String kexalgs = p.getString();
	    System.out.println("- " + kexalgs);
	    String hostkeyalgs = p.getString();
	    System.out.println("- " + hostkeyalgs);
	    String encalgc2s = p.getString();
	    System.out.println("- " + encalgc2s);
	    String encalgs2c = p.getString();
	    System.out.println("- " + encalgs2c);
	    String macalgc2s = p.getString();
	    System.out.println("- " + macalgc2s);
	    String macalgs2c = p.getString();
	    System.out.println("- " + macalgs2c);
	    String compalgc2s = p.getString();
	    System.out.println("- " + compalgc2s);
	    String compalgs2c = p.getString();
	    System.out.println("- " + compalgs2c);
	    String langc2s = p.getString();
	    System.out.println("- " + langc2s);
	    String langs2c = p.getString();
	    System.out.println("- " + langs2c);
	    fupp = p.getBytes(1);
	    System.out.println("- first_kex_follows: " + fupp[0]);
	    /* int32 reserved (0) */

	    SshPacket2 pn = new SshPacket2(SSH2_MSG_KEXINIT);
	    byte[] kexsend = new byte[16];
	    String ciphername;
	    pn.putBytes(kexsend);
	    pn.putString("diffie-hellman-group1-sha1");
	    pn.putString("ssh-rsa");

	    /* FIXME: check if it really is in the encalgc2s */
	    cipher_type = "NONE";
	    ciphername = "none";

	    /* FIXME: dito for HMAC */

	    pn.putString("none");
	    pn.putString("none");
	    pn.putString("hmac-md5");
	    pn.putString("hmac-md5");
	    pn.putString("none");
	    pn.putString("none");
	    pn.putString("");
	    pn.putString("");
	    pn.putByte((byte) 0);
	    pn.putInt32(0);
	    sendPacket2(pn);

	    pn = new SshPacket2(SSH2_MSG_KEXDH_INIT);
	    pn.putMpInt(BigInteger.valueOf(0xdeadbeef));
	    sendPacket2(pn);
	    break;
	}
	case SSH2_MSG_KEXDH_REPLY:
	{
	    String result;

	    System.out.println("SSH2_MSG_KEXDH_REPLY");
	    int bloblen = p.getInt32();
	    System.out.println("bloblen is " + bloblen);
	    /*
	     * the blob has a substructure: String type if RSA: bignum1 bignum2
	     * if DSA: bignum1,2,3,4
	     */
	    String keytype = p.getString();
	    System.out.println("KEXDH: " + keytype);
	    if (keytype.equals("ssh-rsa"))
	    {
		rsa_e = p.getMpInt();
		rsa_n = p.getMpInt();
		result = "\n\rSSH-RSA (" + rsa_n + "," + rsa_e + ")\n\r";
	    }
	    else
	    {
		return "\n\rUnsupported kexdh algorithm " + keytype + "!\n\r";
	    }
	    BigInteger dhserverpub = p.getMpInt();
	    result += "DH Server Pub: " + dhserverpub + "\n\r";

	    /* signature is a new blob, length is Int32. */
	    /*
	     * RSA: String type (ssh-rsa) Int32/byte[] signed signature
	     */
	    int siglen = p.getInt32();
	    String sigstr = p.getString();
	    result += "Signature: ktype is " + sigstr + "\r\n";
	    byte sigdata[] = p.getBytes(p.getInt32());

	    return result;
	}
	default:
	    return "SSH2: handlePacket2 Unknown type " + p.getType();
	}
	return "";
    }

    private String handlePacket1(SshPacket1 p) throws IOException
    { // the message to handle is data and its length is

	byte b; // of course, byte is a signed entity (-128 -> 127)

	// we have to deal with data....

	if (debug > 0)
	    System.out.println("1 packet to handle, type " + p.getType());

	switch (p.getType())
	{
	case SSH_MSG_IGNORE:
	    return "";

	case SSH_MSG_DISCONNECT:
	    String str = p.getString();
	    disconnect();
	    return str;

	case SSH_SMSG_PUBLIC_KEY:
	    byte[] anti_spoofing_cookie; // 8 bytes
	    byte[] server_key_bits; // 32-bit int
	    byte[] server_key_public_exponent; // mp-int
	    byte[] server_key_public_modulus; // mp-int
	    byte[] host_key_bits; // 32-bit int
	    byte[] host_key_public_exponent; // mp-int
	    byte[] host_key_public_modulus; // mp-int
	    byte[] protocol_flags; // 32-bit int
	    byte[] supported_ciphers_mask; // 32-bit int
	    byte[] supported_authentications_mask; // 32-bit int

	    anti_spoofing_cookie = p.getBytes(8);
	    server_key_bits = p.getBytes(4);
	    server_key_public_exponent = p.getMpInt();
	    server_key_public_modulus = p.getMpInt();
	    host_key_bits = p.getBytes(4);
	    host_key_public_exponent = p.getMpInt();
	    host_key_public_modulus = p.getMpInt();
	    protocol_flags = p.getBytes(4);
	    supported_ciphers_mask = p.getBytes(4);
	    supported_authentications_mask = p.getBytes(4);

	    // We have completely received the PUBLIC_KEY
	    // We prepare the answer ...

	    String ret = Send_SSH_CMSG_SESSION_KEY(anti_spoofing_cookie,
		    server_key_public_modulus, host_key_public_modulus,
		    supported_ciphers_mask, server_key_public_exponent,
		    host_key_public_exponent);
	    if (ret != null)
		return ret;

	    // we check if MD5(server_key_public_exponent) is equals to the
	    // applet parameter if any .
	    if (hashHostKey != null && hashHostKey.compareTo("") != 0)
	    {
		// we compute hashHostKeyBis the hash value in hexa of
		// host_key_public_modulus
		byte[] Md5_hostKey = md5.digest(host_key_public_modulus);
		String hashHostKeyBis = "";
		for (int i = 0; i < Md5_hostKey.length; i++)
		{
		    String hex = "";
		    int[] v = new int[2];
		    v[0] = (Md5_hostKey[i] & 240) >> 4;
		    v[1] = (Md5_hostKey[i] & 15);
		    for (int j = 0; j < 1; j++)
			switch (v[j])
			{
			case 10:
			    hex += "a";
			    break;
			case 11:
			    hex += "b";
			    break;
			case 12:
			    hex += "c";
			    break;
			case 13:
			    hex += "d";
			    break;
			case 14:
			    hex += "e";
			    break;
			case 15:
			    hex += "f";
			    break;
			default:
			    hex += String.valueOf(v[j]);
			    break;
			}
		    hashHostKeyBis = hashHostKeyBis + hex;
		}
		// we compare the 2 values
		if (hashHostKeyBis.compareTo(hashHostKey) != 0)
		{
		    login = password = "";
		    return "\nHash value of the host key not correct \r\n"
			    + "login & password have been reset \r\n"
			    + "- erase the 'hashHostKey' parameter in the Html\r\n"
			    + "(it is used for auhentificating the server and "
			    + "prevent you from connecting \r\n"
			    + "to any other)\r\n";
		}
	    }
	    break;

	case SSH_SMSG_SUCCESS:
	    if (debug > 0)
		System.out.println("SSH_SMSG_SUCCESS (last packet was "
			+ lastPacketSentType + ")");
	    if (lastPacketSentType == SSH_CMSG_SESSION_KEY)
	    {
		// we have succefully sent the session key !! (at last :-) )
		Send_SSH_CMSG_USER();
		break;
	    }

	    if (lastPacketSentType == SSH_CMSG_USER)
	    {
		// authentication is NOT needed for this user
		Send_SSH_CMSG_REQUEST_PTY(); // request a pseudo-terminal
		return "\nEmpty password login.\r\n";
	    }

	    if (lastPacketSentType == SSH_CMSG_AUTH_PASSWORD)
	    {// password correct !!!
		// yahoo
		if (debug > 0)
		    System.out.println("login succesful");

		// now we have to start the interactive session ...
		Send_SSH_CMSG_REQUEST_PTY(); // request a pseudo-terminal
		return "\nLogin & password accepted\r\n";
	    }

	    if (lastPacketSentType == SSH_CMSG_REQUEST_PTY)
	    {// pty accepted !!
		/* we can send data with a pty accepted ... no need for a shell. */
		cansenddata = true;
		if (dataToSend != null)
		{
		    Send_SSH_CMSG_STDIN_DATA(dataToSend);
		    dataToSend = null;
		}
		Send_SSH_CMSG_EXEC_SHELL(); // we start a shell
		break;
	    }
	    if (lastPacketSentType == SSH_CMSG_EXEC_SHELL)
	    {// shell is running ...
		/* empty */
	    }

	    break;

	case SSH_SMSG_FAILURE:
	    if (debug > 1)
		System.err.println("SSH_SMSG_FAILURE");
	    if (lastPacketSentType == SSH_CMSG_AUTH_PASSWORD)
	    {// password incorrect ???
		System.out.println("failed to log in");
		Send_SSH_MSG_DISCONNECT("Failed to log in.");
		disconnect();
		return "\nLogin & password not accepted\r\n";
	    }
	    if (lastPacketSentType == SSH_CMSG_USER)
	    {
		// authentication is needed for the given user
		// (in most cases that's true)
		Send_SSH_CMSG_AUTH_PASSWORD();
		break;
	    }

	    if (lastPacketSentType == SSH_CMSG_REQUEST_PTY)
	    {// pty not accepted !!
		break;
	    }
	    break;

	case SSH_SMSG_STDOUT_DATA: // receive some data from the server
	    return p.getString();

	case SSH_SMSG_STDERR_DATA: // receive some error data from the server
	    // if(debug > 1)
	    str = "Error : " + p.getString();
	    System.out.println("SshIO.handlePacket : " + "STDERR_DATA " + str);
	    return str;

	case SSH_SMSG_EXITSTATUS: // sent by the server to indicate that
	    // the client program has terminated.
	    // 32-bit int exit status of the command
	    int value = p.getInt32();
	    Send_SSH_CMSG_EXIT_CONFIRMATION();
	    System.out.println("SshIO : Exit status " + value);
	    disconnect();
	    break;

	case SSH_MSG_DEBUG:
	    str = p.getString();
	    if (debug > 0)
	    {
		System.out.println("SshIO.handlePacket : " + " DEBUG " + str);

		// bad bad bad bad bad. We should not do actions in DEBUG
		// messages,
		// but apparently some SSH demons does not send SSH_SMSG_FAILURE
		// for
		// just USER CMS.
		/*
		 * if(lastPacketSentType==SSH_CMSG_USER) {
		 * Send_SSH_CMSG_AUTH_PASSWORD(); break; }
		 */
		return str;
	    }
	    return "";

	default:
	    System.err.print("SshIO.handlePacket1: Packet Type unknown: "
		    + p.getType());
	    break;

	}// switch(b)
	return "";
    } // handlePacket

    private void sendPacket1(SshPacket1 packet) throws IOException
    {
	write(packet.getPayLoad(crypto));
	lastPacketSentType = packet.getType();
    }

    private void sendPacket2(SshPacket2 packet) throws IOException
    {
	write(packet.getPayLoad(crypto, outgoingseq));
	outgoingseq++;
	lastPacketSentType = packet.getType();
    }

    //
    // Send_SSH_CMSG_SESSION_KEY
    // Create :
    // the session_id,
    // the session_key,
    // the Xored session_key,
    // the double_encrypted session key
    // send SSH_CMSG_SESSION_KEY
    // Turn the encryption on (initialise the block cipher)
    //

    private String Send_SSH_CMSG_SESSION_KEY(byte[] anti_spoofing_cookie,
	    byte[] server_key_public_modulus, byte[] host_key_public_modulus,
	    byte[] supported_ciphers_mask, byte[] server_key_public_exponent,
	    byte[] host_key_public_exponent) throws IOException
    {

	String str;
	int boffset;

	byte cipher_types; // encryption types
	byte[] session_key; // mp-int

	// create the session id
	// session_id = md5(hostkey->n || servkey->n || cookie) //protocol V
	// 1.5. (we use this one)
	// session_id = md5(servkey->n || hostkey->n || cookie) //protocol V
	// 1.1.(Why is it different ??)
	//

	byte[] session_id_byte = new byte[host_key_public_modulus.length
		+ server_key_public_modulus.length
		+ anti_spoofing_cookie.length];

	System.arraycopy(host_key_public_modulus, 0, session_id_byte, 0,
		host_key_public_modulus.length);
	System.arraycopy(server_key_public_modulus, 0, session_id_byte,
		host_key_public_modulus.length,
		server_key_public_modulus.length);
	System.arraycopy(anti_spoofing_cookie, 0, session_id_byte,
		host_key_public_modulus.length
			+ server_key_public_modulus.length,
		anti_spoofing_cookie.length);

	byte[] hash_md5 = md5.digest(session_id_byte);

	// SSH_CMSG_SESSION_KEY : Sent by the client
	// 1 byte cipher_type (must be one of the supported values)
	// 8 bytes anti_spoofing_cookie (must match data sent by the server)
	// mp-int double-encrypted session key (uses the session-id)
	// 32-bit int protocol_flags
	//
	if ((supported_ciphers_mask[3] & (byte) (1 << SSH_CIPHER_BLOWFISH)) != 0)
	{
	    cipher_types = (byte) SSH_CIPHER_BLOWFISH;
	    cipher_type = "Blowfish";
	}
	else
	{
	    if ((supported_ciphers_mask[3] & (1 << SSH_CIPHER_IDEA)) != 0)
	    {
		cipher_types = (byte) SSH_CIPHER_IDEA;
		cipher_type = "IDEA";
	    }
	    else
	    {
		if ((supported_ciphers_mask[3] & (1 << SSH_CIPHER_3DES)) != 0)
		{
		    cipher_types = (byte) SSH_CIPHER_3DES;
		    cipher_type = "DES3";
		}
		else
		{
		    if ((supported_ciphers_mask[3] & (1 << SSH_CIPHER_DES)) != 0)
		    {
			cipher_types = (byte) SSH_CIPHER_DES;
			cipher_type = "DES";
		    }
		    else
		    {
			System.err
				.println("SshIO: remote server does not supported IDEA, BlowFish or 3DES, support cypher mask is "
					+ supported_ciphers_mask[3] + ".\n");
			Send_SSH_MSG_DISCONNECT("No more auth methods available.");
			disconnect();
			return "\rRemote server does not support IDEA/Blowfish/3DES blockcipher, closing connection.\r\n";
		    }
		}
	    }
	}
	if (debug > 0)
	    System.out.println("SshIO: Using " + cipher_type
		    + " blockcipher.\n");

	// anti_spoofing_cookie : the same
	// double_encrypted_session_key :
	// 32 bytes of random bits
	// Xor the 16 first bytes with the session-id
	// encrypt with the server_key_public (small) then the
	// host_key_public(big) using RSA.
	//

	// 32 bytes of random bits
	byte[] random_bits1 = new byte[16], random_bits2 = new byte[16];

	// / java.util.Date date = new java.util.Date(); ////the number of
	// milliseconds since January 1, 1970, 00:00:00 GMT.
	// Math.random() a pseudorandom double between 0.0 and 1.0.
	// random_bits2 = random_bits1 =
	// md5.hash("" + Math.random() * (new java.util.Date()).getDate());
	// md5.digest(("" + Math.random() * (new
	// java.util.Date()).getTime()).getBytes());

	// random_bits1 =
	// md5.digest(SshMisc.addArrayOfBytes(md5.digest((password +
	// login).getBytes()), random_bits1));
	// random_bits2 =
	// md5.digest(SshMisc.addArrayOfBytes(md5.digest((password +
	// login).getBytes()), random_bits2));

	SecureRandom random = new java.security.SecureRandom(random_bits1); // no
									    // supported
									    // by
									    // netscape
									    // :
									    // -
									    // (
	random.nextBytes(random_bits1);
	random.nextBytes(random_bits2);

	session_key = SshMisc.addArrayOfBytes(random_bits1, random_bits2);

	// Xor the 16 first bytes with the session-id
	byte[] session_keyXored = SshMisc.XORArrayOfBytes(random_bits1,
		hash_md5);
	session_keyXored = SshMisc.addArrayOfBytes(session_keyXored,
		random_bits2);

	// We encrypt now!!
	byte[] encrypted_session_key = SshCrypto.encrypteRSAPkcs1Twice(
		session_keyXored, server_key_public_exponent,
		server_key_public_modulus, host_key_public_exponent,
		host_key_public_modulus);

	// protocol_flags :protocol extension cf. page 18
	int protocol_flags = 0; /* currently 0 */

	SshPacket1 packet = new SshPacket1(SSH_CMSG_SESSION_KEY);
	packet.putByte((byte) cipher_types);
	packet.putBytes(anti_spoofing_cookie);
	packet.putBytes(encrypted_session_key);
	packet.putInt32(protocol_flags);
	sendPacket1(packet);
	crypto = new SshCrypto(cipher_type, session_key);
	return "";
    }

    /**
     * SSH_MSG_DISCONNECT string disconnect reason
     */
    private String Send_SSH_MSG_DISCONNECT(String reason) throws IOException
    {
	SshPacket1 p = new SshPacket1(SSH_MSG_DISCONNECT);
	p.putString(reason); // String Disconnect reason
	sendPacket1(p);
	return "";
    }

    /**
     * SSH_CMSG_USER string user login name on server
     */
    private String Send_SSH_CMSG_USER() throws IOException
    {
	if (debug > 0)
	    System.err.println("Send_SSH_CMSG_USER(" + login + ")");

	SshPacket1 p = new SshPacket1(SSH_CMSG_USER);
	p.putString(login);
	sendPacket1(p);

	return "";
    }

    /**
     * Send_SSH_CMSG_AUTH_PASSWORD string user password
     */
    private String Send_SSH_CMSG_AUTH_PASSWORD() throws IOException
    {
	SshPacket1 p = new SshPacket1(SSH_CMSG_AUTH_PASSWORD);
	p.putString(password);
	sendPacket1(p);
	return "";
    }

    /**
     * Send_SSH_CMSG_EXEC_SHELL (no arguments) Starts a shell (command
     * interpreter), and enters interactive session mode.
     */
    private String Send_SSH_CMSG_EXEC_SHELL() throws IOException
    {
	SshPacket1 packet = new SshPacket1(SSH_CMSG_EXEC_SHELL);
	sendPacket1(packet);
	return "";
    }

    /**
     * Send_SSH_CMSG_STDIN_DATA
     * 
     */
    private String Send_SSH_CMSG_STDIN_DATA(String str) throws IOException
    {
	SshPacket1 packet = new SshPacket1(SSH_CMSG_STDIN_DATA);
	packet.putString(str);
	sendPacket1(packet);
	return "";
    }

    /**
     * Send_SSH_CMSG_WINDOW_SIZE string TERM environment variable value (e.g.
     * vt100) 32-bit int terminal height, rows (e.g., 24) 32-bit int terminal
     * width, columns (e.g., 80) 32-bit int terminal width, pixels (0 if no
     * graphics) (e.g., 480)
     */
    private String Send_SSH_CMSG_WINDOW_SIZE(int c, int r) throws IOException
    {
	SshPacket1 p = new SshPacket1(SSH_CMSG_WINDOW_SIZE);

	p.putInt32(r); // Int32 rows
	p.putInt32(c); // Int32 columns
	p.putInt32(0); // Int32 x pixels
	p.putInt32(0); // Int32 y pixels
	sendPacket1(p);
	return "";
    }

    /**
     * Send_SSH_CMSG_REQUEST_PTY string TERM environment variable value (e.g.
     * vt100) 32-bit int terminal height, rows (e.g., 24) 32-bit int terminal
     * width, columns (e.g., 80) 32-bit int terminal width, pixels (0 if no
     * graphics) (e.g., 480)
     */
    private String Send_SSH_CMSG_REQUEST_PTY() throws IOException
    {
	SshPacket1 p = new SshPacket1(SSH_CMSG_REQUEST_PTY);

	p.putString(getTerminalType());
	p.putInt32(24); // Int32 rows
	p.putInt32(80); // Int32 columns
	p.putInt32(0); // Int32 x pixels
	p.putInt32(0); // Int32 y pixels
	p.putByte((byte) 0); // Int8 terminal modes
	sendPacket1(p);
	return "";
    }

    private String Send_SSH_CMSG_EXIT_CONFIRMATION() throws IOException
    {
	SshPacket1 packet = new SshPacket1(SSH_CMSG_EXIT_CONFIRMATION);
	sendPacket1(packet);
	return "";
    }
}
